/* C.Dir: Directory handling */

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"

#include "sys/dir.h"

#define ReadDir 	9

/* Constants. The function assumes that a directory can hold no more than
 * MAX_DIR entries, and a filename can be no longer than MAXNAMELEN (which
 * is defined in the header file to be 10 as a default) characters. It also
 * assumes that OS_GBPB 9 will only ever fail to return all possible
 * filenames on the first call if the supplied buffer becomes full. In this
 * case, dirscan() will ignore any omitted entries. This behaviour is not
 * ideal, but gains significantly in efficiency. (The given buffer size
 * should be easily enough for all normal cases).
 */
#define MAX_DIR		255
#define CACHE_SIZE	(MAX_DIR * (MAXNAMELEN + 1))

DIR *opendir (char *name)
{
	register char *cache;
	register DIR *dirp;
	_kernel_swi_regs regs;

	dirp = malloc(sizeof(DIR));
	if (dirp == 0)
		return NULL;

	cache = malloc(CACHE_SIZE);
	if (cache == NULL)
	{
		free(dirp);
		return NULL;
	}

	regs.r[0] = ReadDir;
	regs.r[1] = (int) name;
	regs.r[2] = (int) cache;
	regs.r[3] = CACHE_SIZE;
	regs.r[4] = 0;
	regs.r[5] = CACHE_SIZE;
	regs.r[6] = (int) "*";

	if (_kernel_swi(OS_GBPB, &regs, &regs))
	{
		free (cache);
		free (dirp);
		return NULL;
	}

	dirp->dd_pos = 0;
	dirp->dd_num = regs.r[3];
	dirp->dd_loc = cache;
	dirp->dd_cache = cache;

	return dirp;
}

struct direct *readdir (DIR *dirp)
{
	register char *from;
	register char *to;
	static struct direct dir;

	/* If no more entries, return NULL */
	if (dirp->dd_pos >= dirp->dd_num)
		return NULL;

	/* Copy the name into the struct direct */
	to = dir.d_name;
	from = dirp->dd_loc;
	while ((*to++ = *from++) != 0)
		;

	/* Update the DIR structure */
	++dirp->dd_pos;
	dirp->dd_loc = from;

	/* Set up the rest of the struct direct */
	dir.d_ino = 0;
	dir.d_reclen = dir.d_namlen = strlen(dir.d_name);

	return &dir;
}

extern void closedir (DIR *dirp)
{
	if (dirp != NULL)
	{
		if (dirp->dd_cache != NULL)
			free(dirp->dd_cache);

		free(dirp);
	}
}

extern int seekdir (DIR *dirp, int pos)
{
	register int old_pos = dirp->dd_pos;
	register int i = 0;
	register char *p = dirp->dd_cache;

	while (i < pos)
	{
		while (*p++ != 0)
			;
		++i;
	}

	dirp->dd_pos = pos;
	dirp->dd_loc = p;

	return old_pos;
}

/* ---------------------------------------------------------------------- */

#ifdef test

#include <ctype.h>
#include <stdio.h>

#define BUFLEN 255

int main (void)
{
	char buf[BUFLEN];
	char s[20];
	int i;
	DIR *dp;

	for ( ; ; )
	{
		printf(">");
		fgets(buf,BUFLEN,stdin);

		if ( buf[0] == '\n' || buf[0] == '\0' )
			continue;
		else if ( sscanf(buf,"dir %s",&s) == 1 )
			dp = opendir(s);
		else if ( strncmp(buf,"dir",3) == 0 )
			dp = opendir("");
		else if ( sscanf(buf,"goto %d",&i) == 1 )
			printf("From: %d\n",seekdir(dp,i));
		else if ( strncmp(buf,"close",5) == 0 )
			closedir(dp);
		else if ( *buf == 'n' )
		{
			int pos = telldir(dp);
			struct direct *d = readdir(dp);
			if (d)
				printf("Pos: %d, Name: %s\n", pos, d->d_name);
			else
				printf("Finished...\n");
		}
		else if ( strncmp(buf,"dump",4) == 0 )
		{
			int max = dp->dd_num * 11;
			char *p = dp->dd_cache;

			printf("Pos: %d\n", dp->dd_pos);
			printf("Num: %d\n", dp->dd_num);
			printf("Loc: %p\n", dp->dd_loc);
			printf("Cache: %p\n", dp->dd_cache);

			while (p < &dp->dd_cache[max])
			{
				if (isgraph(*p) || *p == ' ')
					putchar(*p);
				else
					putchar('.');
				++p;
			}
		}
		else if ( strncmp(buf,"quit",4) == 0 )
			break;
		else if ( *buf == '*' )
			system(&buf[1]);
		else
			printf("Mistake\n");
	}

	return 0;
}

#endif
